#include <system/Resource.h>
#include <system/ResourceManager.h>
#include <system/Log.h>
#include <graphics/Engine.h>

namespace ne
{
    Resource::Resource(ResourceManager *pManager,
                       const uint64_t id,
                       const std::string &name,
                       const std::string &fileName,
                       const bool isManual,
                       ManualResourceLoader *pLoader)
        : mpManager(pManager),
          mID(id), mName(name), mFileName(fileName),
          mSize(0), mIsManual(isManual), mpLoader(pLoader),
          mLoadingState(LOADING_STATE_UNLOADED),
          mIsBackgroundThreadLoading(false)
    {
        if (mFileName == "")
        {
            mFileName = mName;
        }
    }

    Resource::~Resource()
    {
        unload();
    }

    bool Resource::prepare(const bool backgroundThread)
    {
        if (mIsBackgroundThreadLoading && !backgroundThread)
        {
            //TODO: If we mark for background thread loading,
            //      but this is not background thread
            return false;
        }

        if (mLoadingState != LOADING_STATE_UNLOADED)
            //&& mLoadingState != LOADING_STATE_PREPARING)
        {
            Log::Out(LL_WARNING,
                "The state is wrong for [%s] (%s) to prepare", type(), getName().c_str());
            return false;
        }

        mMutex.lock();

        mLoadingState = LOADING_STATE_PREPARING;

        bool success = true;
        if (mIsManual)
        {
            if (mpLoader)
            {
                success = mpLoader->prepareResource(this);
            }
            else
            {
                //TODO: LogMessage : No Loader
                Log::Out(LL_WARNING,
                    "Do not have ManualResourceLoader for [%s] (%s) to prepare", type(), getName().c_str());
                success = false;
            }
        }
        else
        {
            success = prepareImplement();
        }

        mMutex.unlock();

        if (!success)
        {
            mLoadingState = LOADING_STATE_UNLOADED;
            Log::Out(LL_WARNING,
                "Prepare resource [%s] (%s) failed", type(), getName().c_str());
            return false;
        }

        mLoadingState = LOADING_STATE_PREPARED;

        if (mpManager)
        {
            mpManager->onPreparingComplete(this);
        }

        _firePreparingComplete(backgroundThread);
        return true;
    }
    
    bool Resource::load(const bool backgroundThread)
    {
        if (mIsBackgroundThreadLoading && !backgroundThread)
        {
            //TODO: If we mark for background thread loading,
            //      but this is not background thread
            return false;
        }

        if (mLoadingState != LOADING_STATE_UNLOADED &&
            mLoadingState != LOADING_STATE_PREPARED)
        {
            Log::Out(LL_WARNING,
                "The state is wrong for [%s] (%s) to load before prepare", type(), getName().c_str());
            return false;
        }

        if (mLoadingState != LOADING_STATE_PREPARED)
        {
            prepare(backgroundThread);
        }

        if (mLoadingState != LOADING_STATE_PREPARED)
        {
            Log::Out(LL_WARNING,
                "The state is wrong for [%s] (%s) to load after prepare", type(), getName().c_str());
            return false;
        }

        mMutex.lock();

        bool success = true;
        mLoadingState = LOADING_STATE_LOADING;

        if (mIsManual)
        {
            onPreLoad();
            if (mpLoader)
            {
                success = mpLoader->loadResource(this);
            }
            else
            {
                //TODO: LogMessage : No Loader
                Log::Out(LL_WARNING,
                    "Do not have ManualResourceLoader for [%s] (%s) to load", type(), getName().c_str());
                success = false;
            }
            if (success)
            {
                onPostLoad();
            }
        }
        else
        {
            onPreLoad();
            success = loadImplement();
            if (success)
            {
                onPostLoad();
            }
        }

        mMutex.unlock();

        if (!success)
        {
            mLoadingState = LOADING_STATE_PREPARED;
            Log::Out(LL_WARNING,
                "Load resource [%s] (%s) failed", type(), getName().c_str());
            return false;
        }

        mSize = _calculateSize();

        mLoadingState = LOADING_STATE_LOADED;

        if (mpManager)
        {
            mpManager->onLoadingComplete(this);
        }

        _fireLoadingComplete(backgroundThread);
        return true;
    }

    bool Resource::unload(const bool backgroundThread)
    {
        if (mIsBackgroundThreadLoading && !backgroundThread)
        {
            //TODO: If we mark for background thread loading,
            //      but this is not background thread
            return false;
        }

        if (mLoadingState != LOADING_STATE_LOADED)
        {
            //TODO: If state is LOADING_STATE_UNLOADED, no need to unload
            if (mLoadingState != LOADING_STATE_UNLOADED)
            {
                Log::Out(LL_WARNING,
                    "The state is wrong for [%s] (%s) to unload", type(), getName().c_str());
            }
            return false;
        }

        mMutex.lock();

        bool success = true;
        mLoadingState = LOADING_STATE_UNLOADING;

        onPreUnload();
        success = unloadImplement();
        if (success)
        {
            onPostUnload();
        }

        mMutex.unlock();

        if (!success)
        {
            mLoadingState = LOADING_STATE_LOADED;
            Log::Out(LL_WARNING,
                "Unload resource [%s] (%s) failed", type(), getName().c_str());
            return false;
        }

        mLoadingState = LOADING_STATE_UNLOADED;

        if (mpManager)
        {
            mpManager->onUnloadingComplete(this);
        }

        _fireUnloadingComplete(backgroundThread);
        return true;
    }

    bool Resource::reload(const bool backgroundThread)
    {
        if (mLoadingState != LOADING_STATE_UNLOADED)
        {
            unload(backgroundThread);
        }

        if (mLoadingState != LOADING_STATE_UNLOADED)
        {
            Log::Out(LL_WARNING,
                "The state is wrong for [%s] (%s) to reload", type(), getName().c_str());
            return false;
        }

        if (!load(backgroundThread))
        {
            Log::Out(LL_WARNING,
                "Reload resource [%s] (%s) failed", type(), getName().c_str());
            return false;
        }

        return true;
    }

    bool Resource::escalateLoading()
    {
        return load(true);
    }

    bool Resource::download(const bool backgroundThread)
    {
        if (mIsBackgroundThreadLoading && !backgroundThread)
        {
            //TODO: If we mark for background thread downloading,
            //      but this is not background thread
            return false;
        }

        if (mLoadingState != LOADING_STATE_LOADED)
        {
            Log::Out(LL_WARNING,
                "The state is wrong for [%s] (%s) to download", type(), getName().c_str());
            return false;
        }

        mMutex.lock();

        bool success = true;
        mLoadingState = LOADING_STATE_DOWNLOADING;

        onPreDownload();
        success = downloadImplement();
        onPostDownload();

        mMutex.unlock();

        mLoadingState = LOADING_STATE_LOADED;

        if (!success)
        {
            Log::Out(LL_WARNING,
                "Download resource [%s] (%s) failed", type(), getName().c_str());
            return false;
        }

        if (mpManager)
        {
            mpManager->onDownloadingComplete(this);
        }

        _fireDownloadingComplete(backgroundThread);
        return true;
    }

    void Resource::addListener(Listener *pListener)
    {
        mListenerList.push_back(pListener);
    }

    void Resource::removeListener(Listener *pListener)
    {
        mListenerList.remove(pListener);
    }

    FILE* Resource::openFile(const char* mode)
    {
        FILE *file = fopen(getFileName().c_str(), mode);
        if (!file)
        {
            std::string fileName = Engine::GetSingleton().getWorkSpace() + getFileName();
            file = fopen(fileName.c_str(), mode);
        }
        return file;
    }

    int Resource::closeFile(FILE *file)
    {
        return fclose(file);
    }

    void Resource::_firePreparingComplete(const bool backgroundThread)
    {
        for (ListenerListConstIterator it=mListenerList.begin(); it!=mListenerList.end(); ++it)
        {
            if (backgroundThread)
            {
                (*it)->onBackgroundThreadPreparingComplete(this);
            }
            (*it)->onPreparingComplete(this);
        }
    }

    void Resource::_fireLoadingComplete(const bool backgroundThread)
    {
        for (ListenerListConstIterator it=mListenerList.begin(); it!=mListenerList.end(); ++it)
        {
            if (backgroundThread)
            {
                (*it)->onBackgroundThreadLoadingComplete(this);
            }
            (*it)->onLoadingComplete(this);
        }
    }

    void Resource::_fireUnloadingComplete(const bool backgroundThread)
    {
        for (ListenerListConstIterator it=mListenerList.begin(); it!=mListenerList.end(); ++it)
        {
            if (backgroundThread)
            {
                (*it)->onBackgroundThreadUnloadingComplete(this);
            }
            (*it)->onUnloadingComplete(this);
        }
    }

    void Resource::_fireDownloadingComplete(const bool backgroundThread)
    {
        for (ListenerListConstIterator it=mListenerList.begin(); it!=mListenerList.end(); ++it)
        {
            if (backgroundThread)
            {
                (*it)->onBackgroundThreadDownloadingComplete(this);
            }
            (*it)->onDownloadingComplete(this);
        }
    }
}
